#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Version: v2.1
@Date:  2019.1.5
@author: Tan Yuanyuan
@content: make it support python3.x and 32 functions.
"""
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.keys import Keys
import sys
import time
import win32api
import win32con


class Pyse(object):
    """
        Pyse framework for the main class, the original 
    selenium provided by the method of the two packaging,
    making it easier to use.
    """
    pause_time = 1
    timeout = 30

    def __init__(self, browser='ff', profile_path=None, executable_path=None):
        """
        Run class initialization method, the default is proper
        to drive the Firefox browser. Of course, you can also
        pass parameter for other browser, Chrome browser for the "Chrome", 
        the Internet Explorer browser for "internet explorer" or "ie".
        """
        self.browser = browser.lower()
        if self.browser in ("firefox", "ff"):
            if profile_path is None:
                driver = webdriver.Firefox()
            else:
                profile = webdriver.FirefoxProfile(profile_path)
                driver = webdriver.Firefox(profile)
        elif self.browser == "chrome":
            if profile_path is None and executable_path is None:
                driver = webdriver.Chrome()
            else:
                profile = webdriver.ChromeOptions(profile_path)
                driver = webdriver.Chrome(executable_path=executable_path, chrome_options=profile)
        elif self.browser in ("internet explorer", "ie"):
            driver = webdriver.Ie()
        elif self.browser == "opera":
            driver = webdriver.Opera()
        elif self.browser == "phantomjs":
            if executable_path == None:
                driver = webdriver.PhantomJS(service_log_path="D:/watchlog.log")
            else:
                expath = executable_path
                driver = webdriver.PhantomJS(executable_path=expath, service_log_path="./watchlog.log")
        elif self.browser == 'edge':
            driver = webdriver.Edge()
        else:
            raise SyntaxError(
                "Usage: driver = Pyse('ff')\nYou can enter 'ie', 'ff', 'opera', 'phantomjs', 'edge' or 'chrome'.")
        try:
            self.driver = driver
        except Exception:
            print(
                "Not found %s browser,You can enter 'ie', 'ff', 'opera', 'phantomjs', 'edge' or 'chrome'." % self.browser)

    def wait_element(self, el):
        """
        Waiting for an element to display.
        """
        try:
            WebDriverWait(self.driver, self.timeout).until(
                EC.presence_of_element_located(el)
            )
        except TimeoutException:
            return False
        else:
            return True

    def get_element(self, css):
        """
        Judge element positioning way, and returns the element.
        """
        if "=>" not in css:
            by = "css"
            value = css
        else:
            by = css.split("=>")[0]
            value = css.split("=>")[1]
            if by == "" or value == "":
                raise NameError("语法错误，请参考：'id=>useranme'.")

        time_out_error = "定位元素超时，请尝试其他定位方式"
        if by == "id":
            req = self.wait_element((By.ID, value))
            if req is True:
                element = self.driver.find_element_by_id(value)
            else:
                raise TimeoutException(time_out_error)
        elif by == "name":
            req = self.wait_element((By.NAME, value))
            if req is True:
                element = self.driver.find_element_by_name(value)
            else:
                raise TimeoutException(time_out_error)
        elif by == "class":
            req = self.wait_element((By.CLASS_NAME, value))
            if req is True:
                element = self.driver.find_element_by_class_name(value)
            else:
                raise TimeoutException(time_out_error)
        elif by == "link_text":
            req = self.wait_element((By.LINK_TEXT, value))
            if req is True:
                element = self.driver.find_element_by_link_text(value)
            else:
                raise TimeoutException(time_out_error)
        elif by == "xpath":
            req = self.wait_element((By.XPATH, value))
            if req is True:
                element = self.driver.find_element_by_xpath(value)
            else:
                raise TimeoutException(time_out_error)
        elif by == "css":
            req = self.wait_element((By.CSS_SELECTOR, value))
            if req is True:
                element = self.driver.find_element_by_css_selector(value)
            else:
                raise TimeoutException(time_out_error)
        else:
            raise NameError(
                "请输入正确的元素定位方式,'id','name','class','link_text','xpath','css'.")
        return element

    def pause(self, pause_time):
        """
        :Args:
         - pause_time - second.
        """
        if pause_time <= 1:
            time.sleep(1)
        else:
            print("Pause %s s" % pause_time)
            time.sleep(pause_time)

    # 7 method to deal with the basic operator.
    def open(self, url):
        """
        open the URL.
        
        :Args:
         - url - the target URL

        :Usage:
        driver.open("https://www.baidu.com")
        """
        self.driver.get(url)
        print('Opened url %s' % url)

    def max_window(self):
        """
        Set browser window maximized.

        Usage:
        driver.max_window()
        """
        self.driver.maximize_window()
        print('Maximize the %s browser window.' % self.browser)

    def set_window(self, wide, high):
        """
        Set browser window wide and high.

        Usage:
        driver.set_window(wide,high)
        """
        self.driver.set_window_size(wide, high)

    def type(self, css, text):
        """
        Operation input box.

        :Usage:
        driver.type("css=>#el","selenium")
        """
        el = self.get_element(css)
        try:
            el.clear()
        except:
            print('Failed to clear text at %s' % css)
        el.send_keys(text)
        print('Type %s at %s' % (text, css))

    def submit(self, css):
        """
        Submit the specified form.

        Usage:
        driver.submit("css=>#el")
        """
        el = self.get_element(css)
        el.submit()
        print('Click element %s to submit' % xpath)

    def f5(self):
        """
        Refresh the current page.

        :Usage:
        driver.f5()
        """
        self.driver.refresh()
        print('refresh the current page')

    def quit(self):
        """
        Closes the browser and shuts down the driver.

        :Usage:
        driver.quit()
        """
        self.driver.quit()
        print('Quit browser:', self.browser)

    # 5 methhod to deal with the click operator.
    def click_text(self, text):
        """
        Click the element by the link text.

        Usage:
        driver.click_text("新闻")
        """
        self.driver.find_element_by_partial_link_text(text).click()
        print('Open the %s link' % text)

    def click(self, css=None):
        """
        It can click any text / image can be clicked
        Connection, check box, radio buttons, and even drop-down box etc..

        :Usage:
        driver.click("css=>#el")
        """
        if css:
            el = self.get_element(css)
            el.click()
            print("Clicked element:", css)
        else:
            ActionChains(self.driver).click().perform()
            print("Click at the offset.")

    def double_click(self, css):
        """
        double click element.

        :Usage:
        driver.double_click("css=>#el")
        """
        el = self.get_element(css)
        ActionChains(self.driver).double_click(el).perform()
        print('Double click element:', css)

    def right_click(self, css=None, down_num=0):
        """
        Right click element, then use keyboard DOWN array to choose item if needed.

        :Args:
         - down_num - the count of press keyboard DOWN array, it must >=1 if used.

        Usage:
        driver.right_click("css=>#el")
        """
        if css is not None:
            el = self.get_element(css)
            if down_num == 0:
                ActionChains(self.driver).context_click(el).perform()
                print('Right click the center of the element:', css)
            else:
                ActionChains(self.driver).context_click(el).perform()
                # for i in range(down_num):
                #     ActionChains(self.driver).send_keys(Keys.DOWN).perform()
                #     time.sleep(1)
                # ActionChains(self.driver).send_keys(Keys.ENTER).perform()
                for i in range(down_num):
                    win32api.keybd_event(40, 0, 0, 0)
                    win32api.keybd_event(40, 0, win32con.KEYEVENTF_KEYUP, 0)
                    time.sleep(1)
                win32api.keybd_event(13, 0, 0, 0)
                win32api.keybd_event(13, 0, win32con.KEYEVENTF_KEYUP, 0)
                print('Right-click %s element, then cursor down %d times.' % (css, down_num))
        else:
            ActionChains(self.driver).context_click().perform()
            print('Missing the element locator.')

    def select(self, css, value):
        """
        Constructor. A check is made that the given element is, indeed, a SELECT tag. If it is not,
        then an UnexpectedTagNameException is thrown.

        :Args:
         - css - element SELECT element to wrap
         - value - The value to match against

        Usage:
            <select name="NR" id="nr">
                <option value="10" selected="">每页显示10条</option>
                <option value="20">每页显示20条</option>
                <option value="50">每页显示50条</option>
            </select>

            driver.select("#nr", '20')
            driver.select("xpath=>//[@name='NR']", '20')
        """
        el = self.get_element(css)
        Select(el).select_by_value(value)

    # 2 method to deal with the window operator.
    def open_new_window(self, css):
        """
        Open the new window and switch the handle to the newly opened window.

        :Usage:
        driver.open_new_window("link_text=>注册")
        """
        original_window = self.driver.current_window_handle
        el = self.get_element(css)
        el.click()
        all_handles = self.driver.window_handles
        # print(all_handles)
        for handle in all_handles:
            if handle != original_window:
                self.driver.switch_to.window(handle)
                print('Open the new window:', css)

    def select_window(self, window_title):
        """
        Switches focus to the specified window.

        :Args:
         - window_title - the window/tab's title

        Usage:
        driver.select_window('window_title')
        """
        self.driver.switch_to.window(window_title)
        print('Switched window to:', window_title)

    # 2 methods to deal with the iframe operator.
    def enter_frame(self, frame_reference):
        """
        Switches focus to the specified frame, by index, name, or webelement.

        :Args:
         - frame_reference: The name of the window to switch to, an integer representing the index,
                            or a webelement that is an (i)frame to switch to.

        :Usage:
            driver.enter_frame('frame_name')
            driver.enter_frame(1)
            driver.enter_frame("//*[@id='el']")
        """
        if '=>' in frame_reference:
            iframe_el = self.get_element(frame_reference)
            self.driver.switch_to.frame(iframe_el)
        else:
            self.driver.switch_to.frame(frame_reference)
        print('Switched to frame', frame_reference)

    def leave_frame(self):
        """
        Switch focus to the default frame.

        :Usage:
        driver.leave_frame()
        """
        self.driver.switch_to.default_content()

    # 6 methods to deal with the complex operate.
    def mouseOver(self, css):
        """
        Hover on the page element.
        
        :Args:
         - css - the element's css

        :Usage:
        driver.mouseOver("css=>#el")
        """
        el = self.get_element(css)

        if 'chrome' == self.browser:
            ActionChains(self.driver).move_to_element(el).perform()
            print('[chrome]Mouseover at %s' % css)
        # Firefox and IE require multiple cycles, more than twice, to cause a hovering effect.
        elif self.browser in ('ff', 'firefox', 'ie', 'internet explorer'):
            for cycle in range(5):
                ActionChains(self.driver).move_to_element(el).perform()
            print('[ff/ie]Mouseover at %s' % css)
        else:
            print('Other browsers can NOT be tested.')

    def move_by_offset(self, xoffset, yoffset):
        """
        Moving the mouse to an offset from current mouse position.

        :Args:
         - xoffset: X offset to move to, as a positive or negative integer.
         - yoffset: Y offset to move to, as a positive or negative integer.

        :Usage:
        driver.move_by_offset(100, 100)
        """
        ActionChains(self.driver).move_by_offset(xoffset, yoffset).perform()
        print('Move cursor to: %d, %d' % (xoffset, yoffset))

    def focus_element(self, css):
        """
        Make the element displayed on the page.

        :Args:
         - css: the element's css

        :Usage:
        driver.focus_element("link_text=>图片")
        """
        self.js("arguments[0].scrollIntoView();", self.get_element(css))

    def press_keyboard(self, *key_code):
        """
        Mimic system-level keyboard event

        :Args:
         - key_code - such as Keys.TAB, Keys.F11

        Usage:
         driver.press_keyboard(Keys.TAB)
         driver.press_keyboard(Keys.CONTROL, 'a')
        """
        ActionChains(self.driver).send_keys(key_code).perform()
        print('Sends keys  %s successfully.' % key_code)

    def drag_and_drop(self, el_css, ta_css):
        """
        Drag an element a certain distance, then drop it.

        Usage:
        driver.drag_and_drop("css=>#el","css=>#ta")
        """
        element = self.get_element(el_css)
        target = self.get_element(ta_css)
        ActionChains(driver).drag_and_drop(element, target).perform()
        print('Moved element %s to %s' % (el_css, ta_css))

    def js(self, script, *args):
        """
        Execute JavaScript scripts.

        Usage:
        driver.js("window.scrollTo(200,1000);")
        """
        self.driver.execute_script(script, *args)
        print('Execute script: %s' % script)

    # 7 methods to deal with the get contents operator.
    def get_title(self):
        """
        Get window title.

        Usage:
        driver.get_title()
        """
        page_title = self.driver.title
        print('Page title is:', page_title)
        return page_title

    def get_url(self):
        """
        Get the URL address of the current page.

        Usage:
        driver.get_url()
        """
        page_url = self.driver.current_url
        print('Page URL is:', page_url)
        return page_url

    def get_display(self, css, pause_time=1):
        """
        Is the element present on the page
        Here present means visible

        :Args:
         - css - the expected element's css
         - time - wait a moment (in second) before search element on page; minus time means search element at once

        :Usage:
         driver.get_display("css=>#el", 5)
        """
        self.pause(pause_time)
        el = self.get_element(css)
        if el.is_displayed():
            print("Element %s is displayed." % css)
            return True

    def get_text(self, css):
        """
        Get element text information.

        :Usage:
        driver.get_text("css=>#el")
        """
        el = self.get_element(css)
        text = el.text
        print('The element text value is:', text)
        return text

    def get_attribute(self, css, attribute):
        """
        Gets the value of an element attribute.

        Usage:
        driver.get_attribute("css=>#el","type")
        """
        el = self.get_element(css)
        attribute_value = el.get_attribute(attribute)
        print('The value of attribute %s is: %s' % (attribute, attribute_value))
        return attribute_value

    def get_windows_img(self, file_path):
        """
        Get the current window screenshot.

        Usage:
        driver.get_windows_img('/Screenshots/foo.png')
        """
        self.pause(2)
        self.driver.get_screenshot_as_file(file_path)
        print('Get the screenshot: ' + file_path)

    def get_alert_text(self):
        """
        Gets the text of the Alert.

        Usage:
        driver.get_alert_text()
        """
        alert_text = self.driver.switch_to.alert.text
        print("The alert text is: " + alert_text)
        return alert_text

    # 1 method to deal with wait.
    def wait(self, secs=10):
        """
        implicitly wait.All elements on the page.

        Usage:
        driver.wait(10)
        """
        self.driver.implicitly_wait(secs)

    # 2 method to deal with popup window.
    def accept_alert(self):
        """
        accept warning window.

        Usage:
        driver.accept_alert()
        """
        self.driver.switch_to.alert.accept()
        print('Confirm the popup window.')

    def dismiss_alert(self):
        """
        Dismisses the alert window.
        
        Usage:
        driver.dismiss_alert()
        """
        self.driver.switch_to.alert.dismiss()
        print('Dismiss the popup window.')


if __name__ == '__main__':
    driver = Pyse("chrome")
